﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using xplang.parse;
using xplang.abstractions;
using xplang.errors;
using xplang.fs;
using xplang.run;

namespace xplang.contexts
{

  public abstract class Context
  {

    const String NameProperty = "name";

    public Context(FileSystem fs, String name, Context parent, Value scope)
    {
      this.fs = fs;
      this.parent = parent;
      this.name = name;
      this.scope = scope;
    }

    public readonly FileSystem fs;
    public readonly Context parent;
    public readonly String name;
    public readonly Value scope;
    public readonly List<Context> requires = new List<Context>();
    public readonly List<ClassDecl> classes = new List<ClassDecl>();
    public readonly Dictionary<String, Value> objects = new Dictionary<String, Value>();

    public string fullname()
    {
      if (parent == null)
        return name;
      return parent.fullname() + "." + name;
    }

    public void registerType(ClassDecl type)
    {
      if (classes.Any(i => i.name == type.name))
        throw new Exception("Já existe uma classe com este nome");
      else
        classes.Add(type);
    }

    public void registerType(Token name, ClassDecl type)
    {
      if (name.type != TokenType.ttIdentifier && name.type != TokenType.ttInternal)
        new IdentifierExpectedError(name);
      else if (classes.Any(i => i.name == name.text))
        new DuplicateElementError(name);
      else
        classes.Add(type);
    }

    public void registerObject(Token name, Value obj)
    {
      if (name.type != TokenType.ttIdentifier && name.type != TokenType.ttInternal)
        new IdentifierExpectedError(name);
      else if (objects.ContainsKey(name.text))
        new DuplicateElementError(name);
      else
        objects[name.text] = obj;
    }

    public Value createObject(ClassRef type, String name)
    {
      if (objects.ContainsKey(name))
        throw new Exception("Já existe um objeto com o nome "+name);
      Value obj = type.instanciate();
      obj.Set(NameProperty, new Value(xplang.rtl.RTL.stringClass.getRef(null), name));
      objects[name] = obj;
      return obj;
    }

    public ClassDecl findClassDecl(String name)
    {
      ClassDecl r = classes.SingleOrDefault(i => i.name == name);
      if (r == null && parent != null)
        r = parent.findClassDecl(name);
      if (r == null)
      {
        int n = requires.Count;
        for (int i = 0; i < n; i++)
        {
          Context comp = requires[i];
          r = comp.findClassDecl(name);
          if (r != null)
            return r;
        }
      }
      return r;
    }

    public ClassRef findClassRef(String name, ClassRef[] genericParams)
    {
      ClassDecl d = findClassDecl(name);
      return d.getRef(genericParams);
    }

    public void registerUsing(ClassDecl declingClass, ClassDecl used)
    {
      classes.Single(t => t == declingClass);
      fs.advanceCompilation(used);
    }

    public void require(Context context)
    {
      if (requires.IndexOf(context) == -1)
        requires.Add(context);
    }
  }

  public class CompiledContext : Context
  {
    public CompiledContext(FileSystem fs, String name, Context parent) :
      base(fs, name, parent, null)
    {
      require(xplang.rtl.RTL.rtl);
    }
  }

  public abstract class NativeContext : Context
  {
    public NativeContext(FileSystem fs, String name, Context parent) :
      base(fs, name, parent, null)
    {
      NativeContext[] requiredContexts = requirements();
      int n = requiredContexts.Length;
      for (int i = 0; i < n; i++)
        require(requiredContexts[i]);
      NativeClass[] decls = declarations();
      n = decls.Length;
      for (int i = 0; i < n; i++)
        classes.Add(decls[i]);
      for (int i = 0; i < n; i++)
        decls[i].registerMembers();
    }

    public abstract NativeContext[] requirements();
    public abstract NativeClass[] declarations();
  }
}
